﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;

namespace Money.Interface
{
    // TODO how do we handle restoring selection when changing search fields etc (parse working set for those that are visible again?)
    // TODO should right click only happen when over a selected record, or should it work anywhere and change selection if only 1 selected (probably the later)?
    public partial class TransactionGrid : UserControl
    {
        public enum GridColumn
        {
            Unknown,
            Date,
            Account,
            Description,
            Category,
            CategoryGroup,
            Type,
            Amount,
            Edited
        }

        public event EventHandler<TransactionGridArgs> SelectionChanged;
        public event EventHandler<RightClickTransactionGridArgs> RightClick;
        public event EventHandler<TransactionGridArgs> TransactionDoubleClick;
        public event EventHandler<EventArgs> Refreshed;

        public GetTransactionDetailDelegate GetTransactionDetail;

        private List<Transaction> _transactions;
        private List<Transaction> _selectedTransactions = new List<Transaction>();
        private Transaction _currentTransaction = null;

        private List<GridFormatTransactionMatch> _formatRules;

        /// <summary>
        /// we can disable events when certain internal actions are happening
        /// </summary>
        private bool _disableEvents = false;

        private Transaction _displayTopItem;
        private bool _showEdited;

        public override void Refresh()
        {
            base.Refresh();
            OnRefresh();
        }

        public List<Transaction> DataSource
        {
            get
            {
                return _transactions;
            }
            set
            {
                _disableEvents = true;
                List<Transaction> tempSet = _selectedTransactions;
                _selectedTransactions = new List<Transaction>();
                TransactionGridSnapshot();
                _transactions = value;
                _transactionGrid.DataSource = null;
                _transactionGrid.DataSource = _transactions;
                _selectedTransactions = tempSet;
                TransactionGridRestore();

                // now we have populated the data we should select the first item and trigger an event
                _transactionGrid.ClearSelection();
                _disableEvents = false;
                if ( (_transactions != null) && (_transactionGrid.Rows.Count > 0) )
                {
                    _transactionGrid.Rows[0].Selected = true;
                }
            }
        }

        public List<Transaction> SelectedTransactions
        {
            get
            {
                return _selectedTransactions;
            }
        }

        public TransactionGrid()
        {
            InitializeComponent();

            _transactionGrid.AutoGenerateColumns = false;
            ShowEdited = false;
            _editedColumn.Visible = ShowEdited;

            _accountColumn.Tag = GridColumn.Account;
            _amountColumn.Tag = GridColumn.Amount;
            _descriptionColumn.Tag = GridColumn.Description;
            _dateColumn.Tag = GridColumn.Date;
            _categoryColumn.Tag = GridColumn.Category;
            _categoryGroupColumn.Tag = GridColumn.CategoryGroup;
            _editedColumn.Tag = GridColumn.Edited;
            _typeColumn.Tag = GridColumn.Type;
        }

        private void UpdatedSelectedTransactionList()
        {
            _selectedTransactions = new List<Transaction>();
            Transaction firstTransaction = null;

            // TODO there is probably a quicker way of doing this?
            foreach (DataGridViewRow row in _transactionGrid.SelectedRows)
            {
                Transaction transaction = row.DataBoundItem as Transaction;

                if (transaction != null)
                {
                    if (firstTransaction == null)
                    {
                        firstTransaction = transaction;
                    }
                    _selectedTransactions.Add(transaction);
                }
            }

            // get the first record selected
            OnRowSelected(firstTransaction);
        }

        #region Event Triggers for Form

        /// <summary>
        /// Event is triggered when a row has been selected but only 1
        /// </summary>
        /// <param name="transaction"></param>
        private void OnRowSelected(Transaction transaction)
        {
            //UpdatedSelectedTransactionList();

            // dont handle the event if they are presently disabled
            if (_disableEvents)
            {
                return;
            }

            if (SelectionChanged != null)
            {
                TransactionGridArgs eventArgs = new TransactionGridArgs();

                eventArgs.SelectedTransaction = transaction;
                eventArgs.SelectedTransactions = _selectedTransactions;

                SelectionChanged(this, eventArgs);
            }
        }

        /// <summary>
        /// This event is triggered when we want to highlight that the data has been refreshed in the grid
        /// </summary>
        private void OnRefresh()
        {
            UpdatedSelectedTransactionList();

            // dont handle the event if they are presently disabled
            if (_disableEvents)
            {
                return;
            }

            if (Refreshed != null)
            {
                EventArgs eventArgs = new EventArgs();

                Refreshed(this, eventArgs);
            }
        }

        private void OnRightClick(Transaction transaction, GridColumn column)
        {
            UpdatedSelectedTransactionList();

            // dont handle the event if they are presently disabled
            if (_disableEvents)
            {
                return;
            }

            if (RightClick != null)
            {
                RightClickTransactionGridArgs eventArgs = new RightClickTransactionGridArgs();

                eventArgs.SelectedTransaction = transaction;
                eventArgs.SelectedTransactions = _selectedTransactions;
                eventArgs.Column = column;

                RightClick(this, eventArgs);
            }
        }

        private void OnDoubleClick(Transaction transaction = null)
        {
            UpdatedSelectedTransactionList();

            // dont handle the event if they are presently disabled
            if (_disableEvents)
            {
                return;
            }

            if (TransactionDoubleClick != null)
            {
                TransactionGridArgs eventArgs = new TransactionGridArgs();

                eventArgs.SelectedTransaction = transaction;
                eventArgs.SelectedTransactions = _selectedTransactions;

                TransactionDoubleClick(this, eventArgs);
            }
        }

        #endregion

        #region Datagrid Event handlers

        /// <summary>
        /// Called when a cell in the datagrid requires formating
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _transactionGrid_CellFormatting(object sender, DataGridViewCellFormattingEventArgs e)
        {
            DataGridView dataGrid = (DataGridView)sender;

            // TODO have a configurable set of rules that match a transaction and apply a set colour
            if (e.RowIndex >= 0)
            {
                Transaction transaction = dataGrid.Rows[e.RowIndex].DataBoundItem as Transaction;

                if (_formatRules != null)
                {
                    foreach (GridFormatTransactionMatch match in _formatRules)
                    {
                        if (match.MatchRule.IsMatch(transaction))
                        {
                            if (match.Format.Color != Color.Empty)
                            {
                                e.CellStyle.BackColor = match.Format.Color;
                            }
                            if (match.Format.SelectedColor != Color.Empty)
                            {
                                e.CellStyle.SelectionBackColor = match.Format.SelectedColor;
                            }
                            if (match.Format.FontColor != Color.Empty)
                            {
                                e.CellStyle.ForeColor = match.Format.FontColor;
                            }

                            FontStyle fontStyle = FontStyle.Regular;

                            if (match.Format.Bold == true)
                            {
                                fontStyle = FontStyle.Bold;
                            }
                            if (match.Format.Italic == true)
                            {
                                fontStyle |= FontStyle.Italic;
                            }

                            e.CellStyle.Font = new Font(e.CellStyle.Font, fontStyle);
                        }
                    }
                }

                // make the amount cell red if it is negative
                if ((transaction.Amount < 0) && (dataGrid.Columns[e.ColumnIndex].HeaderText.Equals("Amount")))
                {
                    e.CellStyle.ForeColor = Color.Red;
                }

                // display a flag to show that a record has been edited
                if ((transaction.IsDirty) && e.ColumnIndex == _transactionGrid.Columns["_editedColumn"].Index)
                {
                    e.Value = Properties.Resources.edit;
                }

                // HACK we are testing the new formating rules engine
                return;
            }
        }

        /// <summary>
        /// We do not use this at present but it prevents exceptions due to data errors when displaying grid
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _transactionGrid_DataError(object sender, DataGridViewDataErrorEventArgs e)
        {

        }

        /// <summary>
        /// Called when the selection has changed on the datagrid
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _transactionGrid_SelectionChanged(object sender, EventArgs e)
        {
            UpdatedSelectedTransactionList();
        }

        private void _transactionGrid_RowStateChanged(object sender, DataGridViewRowStateChangedEventArgs e)
        {
            switch (e.StateChanged)
            {
                case DataGridViewElementStates.Selected:
                    if (_transactionGrid.SelectedRows.Count == 1)
                    {
                        if (e.Row.Selected == true)
                        {
                            Transaction transaction = e.Row.DataBoundItem as Transaction;

                            if (_currentTransaction != transaction)
                            {
                                if (transaction != null)
                                {
                                    OnRowSelected(transaction);
                                }

                                _currentTransaction = transaction;
                            }
                        }
                    }
                    else
                    {
                        _currentTransaction = null;
                    }
                    break;
            }
        }

        private void _transactionGrid_CellMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            GridColumn column = GridColumn.Unknown;

            // handle right clicks in the grid
            if (e.Button == System.Windows.Forms.MouseButtons.Right)
            {

                if (e.RowIndex >= 0 && e.RowIndex < _transactionGrid.Rows.Count)
                {
                    // if the clicked row is not selected then we need to clear the selection and select the clicked row before doing anything else
                    if (_transactionGrid.Rows[e.RowIndex].Selected == false)
                    {
                        _disableEvents = true;
                        _transactionGrid.ClearSelection();
                        _disableEvents = false;
                        _transactionGrid.Rows[e.RowIndex].Selected = true;
                    }

                    DataGridViewColumn gridColumn = _transactionGrid.Columns[e.ColumnIndex] as DataGridViewColumn;

                    if (gridColumn != null)
                    {
                        GridColumn? columnType = gridColumn.Tag as GridColumn?;

                        if (columnType != null)
                        {
                            column = (GridColumn)columnType;
                        }
                    }

                    Transaction transaction = _transactionGrid.Rows[e.RowIndex].DataBoundItem as Transaction;

                    if (_transactionGrid.Rows[e.RowIndex].Selected == true)
                    {
                        if (transaction != null)
                        {
                            OnRightClick(transaction, column);
                        }
                    }
                }
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _transactionGrid_CellMouseDoubleClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            Transaction transaction = null;

            if (e.RowIndex >= 0 && e.RowIndex < _transactionGrid.Rows.Count)
            {
                transaction = _transactionGrid.Rows[e.RowIndex].DataBoundItem as Transaction;
            }

            OnDoubleClick(transaction);
        }

        /// <summary>
        /// If the column header has been clicked then we need to apply sorting to the data
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void _transactionGrid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
        {
            if (e.Button == System.Windows.Forms.MouseButtons.Left)
            {
                GridColumn column = GridColumn.Unknown;
                TransactionComparer.SortField sortField = TransactionComparer.SortField.None;

                DataGridViewColumn gridColumn = _transactionGrid.Columns[e.ColumnIndex] as DataGridViewColumn;

                if (gridColumn != null)
                {
                    GridColumn? columnType = gridColumn.Tag as GridColumn?;

                    if (columnType != null)
                    {
                        column = (GridColumn)columnType;
                    }
                }

                switch (column)
                {
                    case GridColumn.Account:
                        sortField = TransactionComparer.SortField.Account;
                        break;

                    case GridColumn.Date:
                        sortField = TransactionComparer.SortField.Date;
                        break;

                    case GridColumn.Amount:
                        sortField = TransactionComparer.SortField.Amount;
                        break;

                    case GridColumn.Category:
                        sortField = TransactionComparer.SortField.Category;
                        break;

                    case GridColumn.CategoryGroup:
                        sortField = TransactionComparer.SortField.CategoryGroup;
                        break;

                    case GridColumn.Description:
                        sortField = TransactionComparer.SortField.Description;
                        break;

                    case GridColumn.Edited:
                        sortField = TransactionComparer.SortField.Edited;
                        break;

                    case GridColumn.Type:
                        sortField = TransactionComparer.SortField.Type;
                        break;

                    default:
                        sortField = TransactionComparer.SortField.None;
                        break;
                }

                if (sortField != TransactionComparer.SortField.None)
                {
                    //get the current column details
                    SortOrder strSortOrder = GetSortOrder(e.ColumnIndex);

                    _transactions.Sort(new TransactionComparer(sortField, strSortOrder));
                    _transactionGrid.DataSource = null;
                    _transactionGrid.DataSource = _transactions;
                    //customizeDataGridView();
                    _transactionGrid.Columns[e.ColumnIndex].HeaderCell.SortGlyphDirection = strSortOrder;
                }
            }
        }

        #endregion

        // TODO this does not handle the case where the top transaction dissapears but the selected one is still valid
        // TODO it does not 
        private void TransactionGridSnapshot()
        {
            if (_transactionGrid.FirstDisplayedScrollingRowIndex >= 0)
            {
                if (_transactionGrid.FirstDisplayedScrollingRowIndex < _transactionGrid.Rows.Count)
                {
                    Transaction topTransaction = _transactionGrid.Rows[_transactionGrid.FirstDisplayedScrollingRowIndex].DataBoundItem as Transaction;

                    _displayTopItem = topTransaction;

                    _selectedTransactions.Clear();

                    if (_transactionGrid.SelectedRows.Count > 0)
                    {
                        foreach (DataGridViewRow row in _transactionGrid.SelectedRows)
                        {
                            Transaction transaction = row.DataBoundItem as Transaction;

                            if (transaction != null)
                            {
                                _selectedTransactions.Add(transaction);
                            }
                        }
                    }
                }
            }
        }

        private void TransactionGridRestore()
        {
            List<DataGridViewRow> rows = _transactionGrid.Rows.OfType<DataGridViewRow>().ToList();

            var query = from row in rows
                        join selected in _selectedTransactions on row.DataBoundItem equals selected
                        select row;

            int restoreIndex = 0;

            int minIndex = int.MaxValue;

            if (query.Count() > 0)
            {
                minIndex = query.Min(x => x.Index);
            }

            if (_displayTopItem != null)
            {
                foreach (DataGridViewRow row in _transactionGrid.Rows)
                {
                    if (_displayTopItem == row.DataBoundItem)
                    {
                        restoreIndex = row.Index;
                        break;
                    }
                }
            }

            if (minIndex < restoreIndex || restoreIndex == 0)
            {
                if (minIndex != int.MaxValue)
                {
                    restoreIndex = minIndex;
                }
            }

            if (restoreIndex < _transactionGrid.Rows.Count)
            {
                _transactionGrid.FirstDisplayedScrollingRowIndex = restoreIndex;
            }

            // now try and reselect all of the records that were selected before change of view
            foreach (DataGridViewRow row in query)
            {
                row.Selected = true;
            }
        }

        /// <summary>
        /// Get the current sort order that is being used within the grid for the selected column
        /// </summary>
        /// <param name="columnIndex">Column identifier</param>
        /// <returns>
        /// Sort order
        ///     SortOrder.None
        ///     SortOrder.Descending
        ///     SortOrder.Ascending
        /// </returns>
        private SortOrder GetSortOrder(int columnIndex)
        {
            if (_transactionGrid.Columns[columnIndex].HeaderCell.SortGlyphDirection == SortOrder.None ||
                _transactionGrid.Columns[columnIndex].HeaderCell.SortGlyphDirection == SortOrder.Descending)
            {
                _transactionGrid.Columns[columnIndex].HeaderCell.SortGlyphDirection = SortOrder.Ascending;
                return SortOrder.Ascending;
            }
            else
            {
                _transactionGrid.Columns[columnIndex].HeaderCell.SortGlyphDirection = SortOrder.Descending;
                return SortOrder.Descending;
            }
        }

        public List<GridFormatTransactionMatch> FormatRules
        {
            get { return _formatRules; }
            set { _formatRules = value; }
        }

        public bool ShowEdited
        {
            get { return _showEdited; }
            set
            {
                _showEdited = value;
                _editedColumn.Visible = ShowEdited;

                if (_showEdited)
                {
                    //prevent the red X from appearing in cells without an image
                    _transactionGrid.Columns["_editedColumn"].DefaultCellStyle.NullValue = null;
                }
            }
        }

        private void _transactionGrid_CellMouseEnter(object sender, DataGridViewCellEventArgs e)
        {
            if (e.ColumnIndex >= 0 && e.RowIndex >= 0)
            {
                DataGridView transactionGrid = (DataGridView)sender;

                if (e.RowIndex < transactionGrid.Rows.Count)
                {
                    DataGridViewCell cell = transactionGrid[e.ColumnIndex, e.RowIndex];

                    Transaction transaction = (Transaction)transactionGrid.Rows[e.RowIndex].DataBoundItem;

                    if (transaction != null)
                    {
                        if(GetTransactionDetail != null)
                        {
                            cell.ToolTipText = GetTransactionDetail(transaction);
                        }
                    }
                }
            }
        }

        public void ScrollToSelected()
        {
            int topRow = _transactionGrid.FirstDisplayedScrollingRowIndex;
            int rows = _transactionGrid.DisplayedRowCount(false);

            int rowSelected = _transactionGrid.SelectedRows[0].Index;

            if (rowSelected >= (topRow + rows))
            {
                // work out where we should start displaying from now and add a few rows as padding (i.e. 4)
                _transactionGrid.FirstDisplayedScrollingRowIndex += (rowSelected - (topRow + rows - 4));
            }
        }

    }

    public class TransactionGridArgs : EventArgs
    {
        // TODO visibility of some of these should be updated
        public Transaction SelectedTransaction { get; set; }
        public List<Transaction> SelectedTransactions { get; set; }
    }

    public class RightClickTransactionGridArgs : TransactionGridArgs
    {
        public TransactionGrid.GridColumn Column { get; set; }
    }
}
